<?php
namespace modules;
use \modules\auth\api as auth;
use \phiction\array_utils as arr;
use \phiction\iterator_utils as iter;

class ui
{
    // $controls = [
    //      [ string `text`, string `link` = 'javascript:', ... ]
    // ]
    static function header_control($html, array $controls)
    {
        return $html->mark(['div',
            'class' => 'header-control',
            self::format_anchor_controls($controls),
        ]);
    }

    static function header_control_with_logout($q, &$args, $html, array $controls=[])
    {
        $user = auth::validate($q, $args);
        $username = $user['username'];
        $id       = $user['id'];

        $controls[] = [self::icon_label($username, 'user'), "/user/profile"];
        $controls[] = [self::icon_label('登出所有设备', 'times-circle-o'), 'data-api' => '/api/auth/revoke-my-token', 'data-redirect' => '/auth/login'];
        $controls[] = [self::icon_label('登出', 'sign-out'), 'data-api' => '/api/auth/logout', 'data-redirect' => '/auth/login'];
        return self::header_control($html, $controls);
    }

    static function form($html, string $api, $submit_element, array $fields, array $controls=[], $redirect=null)
    {
        return $html->mark(['form',
            'action' => $api,
            'method' => 'post',
            'data-redirect' => $redirect,

            ['div', 'class' => 'form-body',
                self::format_fields(['.'], $fields, false),
                ['pre', 'class' => 'form-error'],
                ['div', 'class' => 'form-controls',
                    self::format_anchor_controls($controls),
                    ['button', $submit_element],
                ],
            ],
        ]);
    }

    static function readonly_form($html, array $fields)
    {
        return $html->mark(['form',
            ['div', 'class' => 'form-body',
                self::format_fields(['.'], $fields, true),
            ],
        ]);
    }

    static function icon(string $title, string ...$names)
    {
        $fa_names = implode(' ', array_map(function ($name) {
            return "fa-$name";
        }, $names));
        $class = "fa $fa_names";
        return ['span', ['span', 'class' => $class, 'title' => $title, 'aria-hidden' => true]];
    }

    static function icon_label(string $label, string ...$names)
    {
        return ['.',
            self::icon($label, ...$names),
            ['span', $label, 'class' => 'icon-label'],
        ];
    }

    // (string|null) $title
    static function card($body, $title=null): array
    {
        return ['div', 'class' => 'card',
            is_null($title)
                ? null
                : ['div', 'class' => 'card-title', $title],
            ['div', 'class' => 'card-body', $body],
        ];
    }

    static function pager(callable $page_link, int $page, bool $has_next)
    {
        $pager = ['div', 'class' => 'pager'];
        if ($page > 1) $pager[] = [ 'a',  'href' => $page_link($page-1), "上一页" ];
        else           $pager[] = [ 'a', 'class' => 'disabled'         , "上一页" ];
        if ($page < 1 || $has_next) $pager[] = [ 'a',  'href' => $page_link($page+1), "下一页" ];
        else                        $pager[] = [ 'a', 'class' => 'disabled'         , "下一页" ];
        return $pager;
    }

    static function list(array $items): array
    {
        return ['div', 'class' => 'list',
            self::format_items(['.'], $items),
        ];
    }


    #######################################################################
    # formats

    static function format_anchor_controls(array $controls)
    {
        return self::format_controls(
            ['.'], $controls, ['a'],
            1, ['href', 'javascript:']);
    }

    static function format_controls(array $initial, array $controls, array $initial_per_control, ...$ordered)
    {
        // desugar $ordered
        foreach ($ordered as &$expect) {
            if (is_int($expect) || is_string($expect)) {
                $expect = [$expect, null];
            }
            else if (is_array($expect)) {
                $num = count($expect);
                if ($num < 1) throw new \InvalidArgumentException("array must have at least 1 element");
                if ($num > 2) throw new \InvalidArgumentException("array can only have at most 2 elements");
                if ($num === 1) $expect[] = null;
            }
            else {
                throw new \InvalidArgumentException("only support int, string and/or array");
            }
        }

        foreach ($controls as $control) {
            $initial[] = self::format_control($initial_per_control, $control, $ordered);
        }

        return $initial;
    }

    private static function format_control(array $initial, array $control, array $sugarfree_ordered)
    {
        foreach ($sugarfree_ordered as $i => $expect) {
            $initial[$expect[0]] = arr::get_or($control, $i, $expect[1]);
        }

        foreach (iter::str_keyed($control) as $key => $value) {
            $initial[$key] = $value;
        }

        return $initial;
    }

    static function format_fields(array $initial, array $fields, bool $readonly)
    {
        foreach ($fields as $field)
            $initial[] = self::format_field($field, $readonly);
        return $initial;
    }

    static function format_field(array $field, bool $readonly)
    {
        $type = arr::get_or_bad_request($field, 0);
        $placeholder = arr::get_or_bad_request($field, 1);
        $value = arr::get_or_null($field, 2);
        $nullable = arr::get_or($field, 3, false);

        if ($value === '') $value = null;
        if (!is_bool($nullable)) throw new \InvalidArgumentException("nullable can only be true or false.");
        if ($nullable && !$readonly) $placeholder = "（可不填） $placeholder";

        switch ($type) {
            case 'longtext':
                $result = ['textarea',
                    'placeholder' => $placeholder,
                    $value,
                ];
                break;
            case 'password':    // fall through intentionally
            case 'hidden':      // fall through intentionally
            case 'email':       // fall through intentionally
            case 'text':
                $result = ['/input',
                    'type' => $type,
                    'placeholder' => $placeholder,
                    'value' => $value,
                ];
                break;
            case 'int':
                $result = ['/input',
                    'type' => 'number',
                    'placeholder' => $placeholder,
                    'value' => $value,
                ];
                break;
            case 'date':
                if (!is_null($value) && !($value instanceof \DateTime))
                    $value = new \DateTime($value);
                $result = ['/input',
                    'type' => 'date',
                    'placeholder' => $placeholder,
                    'value' => is_null($value) ? null : $value->format("Y-m-d"),
                ];
                break;
            case 'enum':
                if (!is_array($value))
                    throw new \InvalidArgumentException("value of enum must be an array.");

                $name = arr::get_or_bad_request($field, 'name');
                unset($field['name']);

                $default = arr::get_or_null($value, 0);
                if (is_null($default))
                    throw new \InvalidArgumentException("a default value for enum value is required");

                $result = ['div', 'class' => 'radio-box' ];
                foreach (iter::str_keyed($value) as $enum_value => $enum_text) {
                    $checked = false;
                    if ($enum_value === $default) {
                        $checked = true;
                        $default = null;
                    }
                    $result[] = ['label',
                        ['/input', 'type' => 'radio',
                            'name' => $name,
                            'value' => $enum_value,
                            'checked' => $checked,
                            'disabled' => $readonly,
                        ],
                        ['div', 'class' => 'radio-text', $enum_text],
                    ];
                }
                if (!is_null($default))
                    throw new \InvalidArgumentException("default value '$default' is not a valid enum value");

                // HACK: bypass "required" on the parent element
                $nullable = true;

                break;
            default:
                throw new \InvalidArgumentException("unknown form field type: $type");
        }

        $result['required'] = !$nullable;
        $result['readonly'] = $readonly;
        $result = iter::fuse_str_keyed($result, $field);

        return ['label', 'class' => 'field',
            ['div', 'class' => 'field-label', $placeholder],
            $result,
        ];
    }

    static function format_items(array $initial, array $items): array
    {
        foreach ($items as $item)
            $initial[] = self::format_item($item);
        return $initial;
    }

    static function format_item(array $item): array
    {
        $title = arr::get_or_throw($item, 0, new \InvalidArgumentException("list item must have a title"));
        $body = arr::get_or_throw($item, 1, new \InvalidArgumentException("list item must have a body"));
        $controls = arr::get_or($item, 2, []);
        $href = arr::get_or_null($item, 'href');

        return ['div', 'class' => 'list-item',
            ['a', 'class' => 'list-item-link', 'href' => $href,
                ['div', 'class' => 'list-item-title', $title],
                ['div', 'class' => 'list-item-body', $body],
            ],
            ['div', 'class' => 'list-item-controls',
                self::format_anchor_controls($controls),
            ],
        ];
    }
}

